﻿using System;
using System.Configuration;
using System.Reflection;
using System.ServiceProcess;
using Reactor.Customization;
using Reactor.Environment;
using Reactor.Exceptions;
using Reactor.Hosting;
using Reactor.Messaging;
using Reactor.ServiceGrid;
using Reactor.ServiceGrid.Resources;
using Reactor.ServiceGrid.Tools;
using ServiceInstaller = Reactor.ServiceGrid.Tools.ServiceInstaller;

namespace Reactor.ServiceHost
{
    public class ManagedServiceHost : ServiceHostBase
    {
        private const string ServiceDescription = "An autonomous service participating in the Reactor Services Platform and managed by a local Reactor Core.";

        /// <summary>
        /// Initializes a new instance of the <see cref="ManagedServiceHost"/> class.
        /// </summary>
        public ManagedServiceHost()
        {
            GatherServiceInformationFromConfiguration();
        }

        #region Overrides of ServiceHostBase

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        public override void RunManagedService(bool runAsConsole)
        {
            if (Log.IsDebugEnabled) Log.DebugFormat("RunManagedService called. RunAsConsole flag: {0}", runAsConsole);

            InitializePlatform();

            if (runAsConsole)
            {
                Log.Debug("Starting ReactorService directly because of console flag.");

                Context.GridContext.CurrentReactorService.Start();

                Console.WriteLine("Type 'exit' to quit");

                while (Console.ReadLine() != "exit")
                {}

                Context.GridContext.CurrentReactorService.Stop();
            }
            else
            {
                if (Log.IsDebugEnabled) Log.DebugFormat("Handing off execution to the Windows Service. Service name: {0}", Context.GridContext.CurrentReactorService.Identifier.ToServiceInstanceString());

                var windowsServiceWrapper = new ServiceWrapper(Context.GridContext.CurrentReactorService);
                ServiceBase.Run(windowsServiceWrapper);

                if (Log.IsDebugEnabled) Log.Debug("ServiceBase.Run called.");
            }
        }

        public override void Install()
        {
            var servicePath = Assembly.GetEntryAssembly().Location;
            var serviceName = InitializationCustomizationManager.ProposedServiceIdentifier.ToServiceInstanceString();

            if (Log.IsDebugEnabled) Log.DebugFormat("Attempting to install Windows Service: {0}", serviceName);

            if (ServiceInstaller.InstallService(servicePath, serviceName, InitializationCustomizationManager.ProposedServiceIdentifier.ToString(), ServiceDescription, true, false))
                Log.Debug("Windows Service installed.");
            else
                Log.Error("Failed to install Windows Service.");
        }

        public override void Uninstall()
        {
            var serviceName = InitializationCustomizationManager.ProposedServiceIdentifier.ToServiceInstanceString();

            if (Log.IsDebugEnabled) Log.DebugFormat("Attempting to uninstall Windows Service: {0}", serviceName);

            if(!ServiceInstaller.UnInstallService(serviceName))
                Log.ErrorFormat("Failure reported by ServiceInstaller while attempting to uninstall service: {0}", serviceName);
        }

        private void InitializePlatform()
        {
            Log.Debug("Initializing platform by creating an InitializationCustomizationManager and loading customizations.");

            // Allow an opportunity to customize initialization.
            // For all Reactor Services, the service name and version is used for the 
            // primary input queue on the service bus.
            InitializationCustomizationManager.PrimaryEndpointName = PointToPointChannel.CreateNameForManagedServiceInputChannel(InitializationCustomizationManager.ProposedServiceIdentifier);
            var customizationManager = new InitializationCustomizationManager();
            customizationManager.RunAll();
            Log.Info("Initilization customization complete.");

            // Make assertions for required types
            AssertThatReactorServiceImplementationExists();
            AssertThatServiceLocatorExists();
            AssertThatServiceBusExists();
        }

        private void GatherServiceInformationFromConfiguration()
        {
            if(Log.IsDebugEnabled) Log.Debug("Preparing to gather service information from configuration file...");

            /* NOTE: This method uses straight ConfigurationManager methods rather than the Reactor configuration 
                     framework. This is because the IConfigurationAggregator has not yet been created. */

            InitializationCustomizationManager.ProposedServiceIdentifier = new ServiceIdentifier();

            // Find instance identifier, which should be in the application configuration file
            if (!Guid.TryParse(ConfigurationManager.AppSettings[ServiceGridResources.Config_ManagedServiceInstanceIdentifier], out InitializationCustomizationManager.ServiceInstanceIdentifier) || InitializationCustomizationManager.ServiceInstanceIdentifier == Guid.Empty)
                throw new FatalException("Service instance identifier either could not be found or was not a valid Guid.");

            // Find service name, which should also be in the application configuration file
            var serviceName = ConfigurationManager.AppSettings[ServiceGridResources.Config_ManagedServiceName];
            if (string.IsNullOrEmpty(serviceName))
                throw new FatalException("ServiceIdentifier cannot be created because no service name was found in local configuration.");

            // Find service version, which should also be in the application configuration file
            var serviceVersion = ConfigurationManager.AppSettings[ServiceGridResources.Config_ManagedServiceVersion];
            if (string.IsNullOrEmpty(serviceVersion))
                throw new FatalException("ServiceIdentifier cannot be created because no service version was found in local configuration.");

            Version v;
            if (!Version.TryParse(serviceVersion, out v))
                throw new FatalException("ServiceIdentifier cannot be created because the service version found in local configuration could not be parsed into a valid Version object.");

            InitializationCustomizationManager.ProposedServiceIdentifier.Name = serviceName;
            InitializationCustomizationManager.ProposedServiceIdentifier.Version = v;

            if(Log.IsDebugEnabled) Log.DebugFormat("Finished gathering service information from configuration. Discovered values: Service name: {0} Version: {1}", serviceName, serviceVersion);
        }

        private static void AssertThatReactorServiceImplementationExists()
        {
            if (Context.GridContext.CurrentReactorService == null)
                throw new FatalException("No IReactorService found on GridContext. Provide an initialization customizer that in turn provides a service instance.");

            if (Context.GridContext.CurrentReactorService.Identifier == null)
                throw new FatalException("IReactorService instance does not contain an Identifier.");
        }

        #endregion
    }
}
